package program_storage;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * The Lexer class provides an iterator over tokens for a given character
 * stream.
 */

public class Lexer implements Iterator<Token> {

	/**
	 * Map containing all keywords {@link TokenType}
	 */
	private static final Map<String, TokenType> keywords = new HashMap<String, TokenType>();

	/**
	 * Initializes the maps
	 */
	static {
		for (TokenType t : TokenType.values()) {
			String text = t.getText();
			if (t.isKeyword())
				keywords.put(text, t);
		}
	}
	
	/**
	 * End of File Constant
	 */
	private static final char eofChar = TokenType.EOF.getText().charAt(0);
	
	/**
	 * The Reader containing the Bytecode-File
	 */
	private final Reader reader;
	
	/**
	 * Current Character
	 */
	private char ch = ' ';
	
	/**
	 * Line of current Character
	 */
	private int line = 1;
	
	/**
	 * Column of current Character
	 */
	private int col = 1;

	/**
	 * Creates a new Lexer for the given reader.
	 *
	 * @param reader
	 *            reader for which the tokens are produced.
	 */
	public Lexer(Reader reader) {
		this.reader = reader;
		read();
	}

	/**
	 * Reads and returns the next character.
	 *
	 * @return the read character
	 */
	private int read() {
		try {
			int val = reader.read();
			col = col + 1;
			ch = val >= 0 ? (char) val : eofChar;
		} catch (IOException e) {
			ch = eofChar;
		}
		return ch;
	}

	/**
	 * This method skips the whitespace from the reader. If the actual character
	 * is an whitespace character it read as long as it reaches a non whitespace
	 * character.
	 */
	private void skipWhitespace() {
		while (Character.isWhitespace(ch)) {
			if (ch == '\n') {
				col = 0;
				line = line + 1;
			}
			read();
		}
	}

	/**
	 * Returns the next token.
	 *
	 * @see java.util.Iterator#next()
	 */
	public Token next() {
		skipWhitespace();

		if (Character.isLetter(ch)) {
			StringBuilder sb = new StringBuilder();
			int col = this.col;

			do {
				sb.append(ch);
				read();
			} while (Character.isLetterOrDigit(ch));

			String s = sb.toString();
			TokenType type = keywords.get(s);
			return new Token(line, col, type, sb.toString());
		}

		else if (Character.isDigit(ch)) {
			StringBuilder sb = new StringBuilder();
			int col = this.col;
			do {
				sb.append(ch);
				read();
			} while (Character.isDigit(ch));
			if (ch != '.') {
				return new Token(line, col, TokenType.INT_LITERAL, sb.toString().intern());
			}
			do {
				sb.append(ch);
				read();
			} while (Character.isDigit(ch));
			
			return new Token(line, col, TokenType.DOUBLE_LITERAL, sb.toString().intern());
		}
		
		else if (ch == ':') {
			StringBuilder sb = new StringBuilder();
			int col = this.col;
			sb.append(ch);
			read();
			return new Token(line, col, TokenType.COLON, sb.toString().intern());
		}
		
		else if (ch == ';') {
			StringBuilder sb = new StringBuilder();
			int col = this.col;
			sb.append(ch);
			read();
			return new Token(line, col, TokenType.SEMI, sb.toString().intern());
		}
		
		else if (ch == '\uffff') {
			StringBuilder sb = new StringBuilder();
			int col = this.col;
			sb.append(ch);
			read();
			return new Token(line, col, TokenType.EOF, sb.toString().intern());
		}
		
		return Token.ERROR;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Iterator#hasNext()
	 */
	public boolean hasNext() {
		return ch != eofChar;
	}

	/**
	 * This method will throw an {@link UnsupportedOperationException}.
	 *
	 * @see java.util.Iterator#remove()
	 */

	public void remove() {
		throw new UnsupportedOperationException();
	}

}
